# --------------------------------------------------------------------------------
#  NoiseTube Web application
#
#  Copyright (C) 2008-2010 SONY Computer Science Laboratory Paris
#  Portions contributed by Vrije Universiteit Brussel (BrusSense team), 2008-2011
# --------------------------------------------------------------------------------
#  This library is free software; you can redistribute it and/or modify it under
#  the terms of the GNU Lesser General Public License, version 2.1, as published
#  by the Free Software Foundation.
#
#  This library is distributed in the hope that it will be useful, but WITHOUT
#  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
#  details.
#
#  You should have received a copy of the GNU Lesser General Public License along
#  with this library; if not, write to:
#    Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor,
#    Boston, MA  02110-1301, USA.
#
#  Full GNU LGPL v2.1 text: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
#  NoiseTube project source code repository: http://code.google.com/p/noisetube
# --------------------------------------------------------------------------------
#  More information:
#   - NoiseTube project website: http://www.noisetube.net
#   - Sony Computer Science Laboratory Paris: http://csl.sony.fr
#   - VUB BrusSense team: http://www.brussense.be
# --------------------------------------------------------------------------------

require 'maps/process_maps'
require 'shapefile'
require 'net/ftp'
require 'net/http'
require 'uri'

class AggregateMap < ActiveRecord::Base
  belongs_to :city
  belongs_to :user

  attr_accessible   :name, :user_id, :user, :jsontext, :generated, :made_at, :city_id, :start_time, :end_time, :comments , :bounds,
                    :maxdb, :mindb, :meandb, :stddevdb, :measures_size, :start_hour, :end_hour

  scope :generated_maps , :select=>"*",
                          :joins=>:users,
                          :joins=>:city,
                          :conditions=>"generated = true",
                          :order=>"users.login ASC, made_at DESC"

  scope :ungenerated_maps , :select=>"*",
        :joins=>:users,
        :joins=>:cities,
        :conditions=>"generated = false",
        :order=>"users.login ASC, made_at DESC"

  #immediately generates the map according to saved params
  def process_map
     exec_generate_map
  end

  def set_bounding_box(xmin,ymin, xmax,ymax)
     # workaround - create two measures and feed to geodata lib
    g1 = Measure.geo_from_loc("#{xmin},#{ymin}")
    g2 = Measure.geo_from_loc("#{xmax},#{ymax}")

    bbox = GeoFeatures::to_box([g1,g2])
    self.bounds = bbox
  end

  def self.process_aggmaps(send_email = false)
    aggmaps = where("generated = false")
    #save to db
    aggmaps.each {|map| map.process_map}

    if send_email
      aggmaps.each {|map|
        unless map.user.nil?
         puts "Queue email send for aggmap owner " << map.user.try(:login)
         UserMailer.delay.deliver_new_aggmap(map)
        end
      }
    end
  end

  # -----------------------------------------------------

  private

  # gets a city's measurements within specified times (potentially long-running)
  # @param [Object] cityid
  # @param [Object] start_time
  # @param [Object] end_time
  def exec_generate_map

    map_measures =  Measure.scoped

    extents = GeoFeatures::polygon_extents(self.bounds)


    #check if generated already
    unless self.generated
      measures = []

      map_measures = map_measures.scoped
      #time
      map_measures = map_measures.where(" measures.made_at >= ? ", self.start_time.to_s(:db)) unless self.start_time.blank?
      map_measures = map_measures.where(" measures.made_at <= ? ", self.end_time.to_s(:db)) unless self.end_time.blank?
      #hours
      map_measures = map_measures.where(" EXTRACT(HOUR FROM measures.made_at) >= ? ", self.start_hour.hour) unless self.start_hour.blank?
      map_measures = map_measures.where(" EXTRACT(HOUR FROM measures.made_at) <= ? ", self.end_hour.hour) unless self.end_hour.blank?
      #geomeasures
      mam_measures = map_measures.where(" measures.geom is not null ")
      map_measures = map_measures.where(" measures.geom && ST_MakeEnvelope(?, ?, ?, ?, ?)", extents[:minx],extents[:miny].try(:to_d), extents[:maxx], extents[:maxy].try(:to_d), GeoFeatures::PROJECTION_SRID) unless self.bounds.blank?

      #map_measures = map_measures.where(" geom is not null", self.end_time.to_s(:db)) unless self.end_time.blank?

      new_measures = map_measures.all

      # add the measures from db to our array, unless they are blank
      # using push instead of concat avoids the creation of a third array
      measures << new_measures unless new_measures.blank?

      #flatten
      measures.flatten!

      #geomeasures
      #geomeasures = measures.select{|m| !m.geom.nil?}

      #save that its generated first - if anything goes wrong, we save in comments
      self.generated = true
      self.comments = "Processing incomplete"    #todo: post error in column if sthng goes wrong,
                                 # already in the logs and delayed_jobs table
      self.json_text = "[]"
      self.made_at = Time.now
      self.save
      #directory for shape files
      shape_file_name = Shapefile::create_filename_for_map(self)

      file = Shapefile::create_shapefile(SHAPE_FILES_PATH + shape_file_name)
      puts measures.size
      if measures.size > 0

        #intensive operation
        result = ProcessMaps.aggregatedmap(measures)

        stats_results = result.inject({countmeasures: 0, sumdb: 0, maxdb: 0, mindb: 199}) { |a, e|
          a[:countmeasures] += e[:count] unless e[:count].nil?
          a[:sumdb] += e[:sum] unless e[:sum].nil?
          a[:maxdb] = e[:maxdb] if e[:maxdb] > a[:maxdb]  unless e[:maxdb].nil?
          a[:mindb] = e[:mindb] if e[:mindb] < a[:mindb]  unless e[:mindb].nil?
          a
        }
        unless stats_results.blank?

          Shapefile::write_array_to_shapefile(file, result, self.start_time, self.end_time)

          #create a zip file
          Shapefile::zip_shapefiles(shape_file_name)

          folder = Shapefile::get_folder_for_map(self).split('/')
          pilot_folder = folder[0]
          subarea_folder = folder[1] if folder.length == 2

          
         # Upload to FTP server
          shapefile_location = SHAPE_FILES_PATH + shape_file_name
          Net::FTP.open(ISCOPE_SERVER_DOMAIN_NAME, ISCOPE_SERVER_FTP_LOGIN, ISCOPE_SERVER_FTP_PASSWORD) do |ftp|
          ftp.passive = true
 
          # Test if the pilot folder exist
          ftp.mkdir(ISCOPE_SERVER_PATH + pilot_folder) if !ftp.list(ISCOPE_SERVER_PATH).any?{|dir| dir.match(/\s#{pilot_folder}$/)}
          ftp.chdir(ISCOPE_SERVER_PATH + pilot_folder)

          if subarea_folder
            # Test if the SUBAREA folder exist
            ftp.mkdir(ISCOPE_SERVER_PATH + pilot_folder + "/" + subarea_folder) if !ftp.list(ISCOPE_SERVER_PATH + pilot_folder).any?{|dir| dir.match(/\s#{subarea_folder}$/)}
            ftp.chdir(ISCOPE_SERVER_PATH + pilot_folder + "/" + subarea_folder)
          end

          # Upload the files
          ftp.putbinaryfile(shapefile_location + ".shp")
          ftp.putbinaryfile(shapefile_location + ".dbf")
          ftp.putbinaryfile(shapefile_location + ".shx")
          ftp.putbinaryfile(shapefile_location + ".prj")
          end
          

          # Inform the iscope Server by doing a HTTP PUT request in the following way:
          # http://iscopeservices.sinergis.it/wsprocess/iscope/noise?PILOT=name1&SUB_AREA=name2
          puts "/wsprocess/iscope/noise?PILOT=#{pilot_folder}&SUB_AREA=#{if subarea_folder then subarea_folder else pilot_folder end}"
          url = 'http://iscopeservices.sinergis.it/wsprocess/iscope/noise'
          uri = URI.parse(url)
          http = Net::HTTP.new(uri.host, uri.port)
          request = Net::HTTP::Put.new("/wsprocess/iscope/noise?PILOT=#{pilot_folder}&SUB_AREA=#{if subarea_folder then subarea_folder else pilot_folder end}")
          response = http.request(request) # the actual PUT request
          puts response
          #request.set_form_data({"PILOT" => pilot_folder, "SUB_AREA" => subarea_folder})


          count = stats_results[:countmeasures]
          stats_results[:mean] = (stats_results[:sumdb] / count) unless count == 0
          self.maxdb = stats_results[:maxdb]
          self.mindb = stats_results[:mindb]

          #mindb
          self.mindb = nil if self.mindb == 199

          self.measures_size = count
          self.meandb = stats_results[:mean]
        end
        if measures.size > 0
          self.comments = "json"
        else
          self.comments = "no gis measures"
        end
        self.bounds = GeoFeatures::to_box(measures.map(&:geom))
        self.json_text = result.as_json.try(:to_json)
        self.generated = true


      else   # no measures
        self.json_text = "[]"
        self.measures_size = 0
        self.comments = "no measures"
      end

      #save
      self.made_at = Time.now
      self.generated = true
      self.save
    end
  end

end